package librpc

import (
	"fmt"
	"errors"
	"reflect"
	"math/big"
	"kumachan/standalone/rx"
	"kumachan/standalone/rpc/vd"
	"kumachan/interpreter/runtime/rt"
	"kumachan/interpreter/runtime/lib/container"
)


type VdTypedValue struct {
	Type   *vd.Type
	Value  Value
}
type vdFieldValue struct {
	Name   string
	Value  VdTypedValue
}
type vdRecordDraft ([] vdRecordDraftItem)
type vdRecordDraftItem struct {
	HasValue  bool
	Value     Value
}

type vdTransformContext struct {
	config   rt.VdConf
	machine  AbstractMachine
	vdUtilityFunctions
}
type vdUtilityFunctions struct {
	validators  map[vd.ValidatorId] func(vd.Object)(bool)
	adapters    map[vd.AdapterId] func(vd.Object)(vd.Object)
}
func (ctx *vdTransformContext) lookupSchema(id vd.TypeId) (vd.Schema, bool) {
	var t, exists = ctx.config.SchemaTable[id]
	return t, exists
}
func (ctx *vdTransformContext) getSchema(id vd.TypeId) vd.Schema {
	var t, exists = ctx.config.SchemaTable[id]
	if !(exists) { panic(fmt.Sprintf("schema not found for %s", id)) }
	return t
}
func (ctx *vdTransformContext) getTypeFromId(id vd.TypeId) *vd.Type {
	return ctx.config.GetTypeFromId(id)
}
func (ctx *vdTransformContext) getValidator(id vd.ValidatorId) (func(vd.Object)(bool), bool) {
	var v, exists = ctx.validators[id]
	return v, exists
}
func (ctx *vdTransformContext) getAdapter(id vd.AdapterId) (func(vd.Object)(vd.Object), bool) {
	var a, exists = ctx.adapters[id]
	return a, exists
}
func (ctx *vdTransformContext) getConstValue(sym rt.Symbol) vd.Object {
	var f = ctx.machine.MustGetFuncValue(sym)
	var v = ctx.machine.Call(rx.Background(), f, nil)
	return v
}

func makeVdTransformer(ctx *vdTransformContext) vd.Transformer {
	var validate = func(obj vd.Object, t vd.TypeId) error {
		var f, exists = ctx.getValidator(vd.ValidatorId(t))
		if exists {
			if f(obj) {
				return nil
			} else {
				return errors.New(fmt.Sprintf(
					"validation failed for type %s", t))
			}
		} else {
			return nil
		}
	}
	return vd.Transformer {
		Serializer: &vd.Serializer {
			ReflectInward: func(obj vd.Object) vd.Object {
				return obj
			},
			DetermineType: func(obj vd.Object) *vd.Type {
				return obj.(VdTypedValue).Type
			},
			PrimitiveSerializer: vd.PrimitiveSerializer {
				WriteBool: func(obj vd.Object) bool {
					var tv = obj.(VdTypedValue)
					return rt.FromBool(tv.Value.(rt.EnumValue))
				},
				WriteFloat: func(obj vd.Object) float64 {
					return obj.(VdTypedValue).Value.(float64)
				},
				WriteComplex: func(obj vd.Object) complex128 {
					return obj.(VdTypedValue).Value.(complex128)
				},
				WriteInteger: func(obj vd.Object) *big.Int {
					return obj.(VdTypedValue).Value.(*big.Int)
				},
				WriteString: func(obj vd.Object) string {
					return obj.(VdTypedValue).Value.(string)
				},
				WriteBinary: func(obj vd.Object) ([] byte) {
					return obj.(VdTypedValue).Value.([] byte)
				},
			},
			ContainerSerializer: vd.ContainerSerializer {
				IterateArray: func(obj vd.Object, f func(uint, vd.Object) error) error {
					var tv = obj.(VdTypedValue)
					var list = container.ListFrom(tv.Value)
					return list.ForEachWithError(func(i uint, item_v Value) error {
						var item_t = tv.Type.ElementType()
						var item_tv = VdTypedValue {
							Type:  item_t,
							Value: item_v,
						}
						return f(i, item_tv)
					})
				},
				UnwrapMaybe: func(obj vd.Object) (vd.Object, bool) {
					var tv = obj.(VdTypedValue)
					var sv = tv.Value.(rt.EnumValue)
					var inner, ok = rt.Unwrap(sv)
					if ok {
						return VdTypedValue {
							Type:  tv.Type.ElementType(),
							Value: inner,
						}, true
					} else {
						return nil, false
					}
				},
			},
			AlgebraicSerializer: vd.AlgebraicSerializer {
				IterateRecord: func(obj vd.Object, f func(string, vd.Object) error) error {
					var tv = obj.(VdTypedValue)
					// TODO: return on nil
					var pv = tv.Value.(rt.TupleValue)
					var tid = tv.Type.Identifier()
					var t = ctx.getSchema(tid)
					var schema = t.(vd.RecordSchema)
					var buffer = make([] vdFieldValue, len(schema.Fields))
					for name, field := range schema.Fields {
						var field_t = field.Type
						var field_v = pv.Elements[field.Index]
						buffer[field.Index] = vdFieldValue {
							Name:  name,
							Value: VdTypedValue {
								Type:  field_t,
								Value: field_v,
							},
						}
					}
					for _, field := range buffer {
						var err = f(field.Name, field.Value)
						if err != nil { return err }
					}
					return nil
				},
				Enum2Case: func(obj vd.Object) vd.Object {
					var tv = obj.(VdTypedValue)
					var sv = tv.Value.(rt.EnumValue)
					var tid = tv.Type.Identifier()
					var t = ctx.getSchema(tid)
					var schema = t.(vd.EnumSchema)
					// TODO: should have a reversed mapping to lookup
					for case_tid, index := range schema.CaseIndexMap {
						if index == uint(sv.Index) {
							var case_t = ctx.getTypeFromId(case_tid)
							var case_v = sv.Value
							return VdTypedValue {
								Type:  case_t,
								Value: case_v,
							}
						}
					}
					panic("something went wrong")
				},
			},
		},
		Deserializer: &vd.Deserializer {
			ReflectOutward: func(obj vd.Object) vd.Object {
				return obj
			},
			PrimitiveDeserializer: vd.PrimitiveDeserializer {
				Unit: nil,
				ReadBool: func(v bool) vd.Object {
					return rt.ToBool(v)
				},
				ReadFloat: func(v float64) vd.Object { return v },
				ReadComplex: func(v complex128) vd.Object { return v },
				ReadInteger: func(v *big.Int) (vd.Object, bool) {
					return v, true
				},
				ReadString: func(v string) vd.Object { return v },
				ReadBinary: func(v ([] byte)) vd.Object { return v },
			},
			ContainerDeserializer: vd.ContainerDeserializer {
				CreateArray: func(array_t *vd.Type) (vd.Object, error) {
					switch array_t.ElementType().Kind() {
					case vd.Bool:    return make([] bool, 0), nil
					case vd.Float:   return make([] float64, 0), nil
					case vd.Complex: return make([] complex128, 0), nil
					default:          return make([] Value, 0), nil
					}
				},
				AppendItem: func(array_ptr *vd.Object, item vd.Object) error {
					var array_v = reflect.ValueOf(*array_ptr)
					var item_v = reflect.ValueOf(item)
					var appended_v = reflect.Append(array_v, item_v)
					var appended = appended_v.Interface()
					*array_ptr = appended
					return nil
				},
				MaybeSome: func(obj vd.Object, _ *vd.Type) (vd.Object, error) {
					return rt.Some(obj), nil
				},
				MaybeNone: func(_ *vd.Type) (vd.Object, error) {
					return rt.None(), nil
				},
			},
			AlgebraicDeserializer: vd.AlgebraicDeserializer {
				AssignObject: func(obj vd.Object, from *vd.Type, to *vd.Type) (vd.Object, error) {
					if vd.TypeEqual(from, to) {
						return obj, nil
					} else if from.Identifier() != (vd.TypeId {}) &&
						to.Identifier() != (vd.TypeId {}) {
						var adapter_id = vd.AdapterId {
							From: from.Identifier(),
							To:   to.Identifier(),
						}
						var adapt, exists = ctx.getAdapter(adapter_id)
						if exists {
							var adapted = adapt(obj)
							return adapted, nil
						} else {
							return nil, errors.New(fmt.Sprintf(
								"the type %s cannot be adapted to the type %s",
								from, to))
						}
					} else {
						return nil, errors.New(fmt.Sprintf(
							"the type %s cannot be assigned to the type %s",
							from, to))
					}
				},
				CreateRecord: func(record_t vd.TypeId, _ uint) (vd.Object, func(string)(vd.RecordField,bool), error) {
					var sch, exists = ctx.lookupSchema(record_t)
					if !(exists) { return nil, nil, errors.New(fmt.Sprintf(
						"type %s does not exist", record_t)) }
					var rec, ok = sch.(vd.RecordSchema)
					if !(ok) { return nil, nil, errors.New(fmt.Sprintf(
						"type %s is not a record type", record_t)) }
					var obj = make(vdRecordDraft, len(rec.Fields))
					var defaults, has_defaults =
						ctx.config.VdDefaultsMapping[record_t]
					if has_defaults {
						for _, field := range rec.Fields {
							var index = field.Index
							var sym, field_has_default = defaults[index]
							if field_has_default {
								obj[index] = vdRecordDraftItem {
									HasValue: true,
									Value:    ctx.getConstValue(sym),
								}
							}
						}
					}
					var fields = func(name string) (vd.RecordField, bool) {
						var field, exists = rec.Fields[name]
						return field, exists
					}
					return obj, fields, nil
				},
				FillField: func(obj vd.Object, index uint, value vd.Object) {
					var draft = obj.(vdRecordDraft)
					draft[index] = vdRecordDraftItem {
						HasValue: true,
						Value:    value,
					}
				},
				FinishRecord: func(obj vd.Object, t vd.TypeId) (vd.Object, error) {
					var draft = obj.(vdRecordDraft)
					var values = make([] Value, len(draft))
					// TODO: should return nil when len(draft) == 0
					for i, item := range draft {
						if item.HasValue {
							values[i] = item.Value
						} else {
							return nil, errors.New(fmt.Sprintf(
								"bad record: missing value for field #%d", i,
							))
						}
					}
					var record = rt.TupleOf(values)
					err := validate(record, t)
					if err != nil { return nil, err }
					return record, nil
				},
				Case2Enum: func(obj vd.Object, enum_tid vd.TypeId, case_tid vd.TypeId) (vd.Object, error) {
					var enum_t, exists = ctx.lookupSchema(enum_tid)
					if !(exists) { return nil, errors.New(fmt.Sprintf(
						"type %s does not exist", enum_tid)) }
					var schema, ok = enum_t.(vd.EnumSchema)
					if !(ok) { return nil, errors.New(fmt.Sprintf(
						"type %s is not a enum type", enum_tid)) }
					var index, is_case = schema.CaseIndexMap[case_tid]
					if !(is_case) { return nil, errors.New(fmt.Sprintf(
						"type %s is not a case type of the enum type %s",
						case_tid, enum_tid)) }
					return &rt.ValEnum {
						Index: rt.EnsureShortIndex(index),
						Value: obj,
					}, nil
				},
			},
		},
	}
}


